/*
 * Copyright (C) 2012-2013 Michael L.R. Marques
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 * Contact: michaellrmarques@gmail.com
 */

package com.jm.commons.logging;

import com.jm.commons.ui.UIBounds;
import com.jm.commons.utils.Constants;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GraphicsEnvironment;
import java.awt.print.PrinterException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;

/**
 * 
 * @author Michael L.R. Marques
 */
public class LogSystem extends javax.swing.JDialog {
    
    /**
     * The default date format
     */
    private static SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy");
    
    /**
     * The default time format
     */
    private static SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
    
    /**
     * The default log file
     */
    private static File logFile = new File("./logging/Log - " + getDate() + ".txt");
    
    /**
     * Logging flag that defines is the LogSystem is allowed to log
     */
    private static boolean isLogging = false;

    /**
     * Creates new form LogSystem
     * 
     * @param parent 
     */
    private LogSystem(Frame parent) {
        super(parent);
        initComponents();
        if (parent != null) {
            setBounds(UIBounds.getBounds(parent, getBounds()));
        }
        // Set the title
        try {
            setTitle(getTitle() + " - " + logFile.getCanonicalPath());
        } catch (IOException ioe) {
            setTitle(getTitle() + " - " + logFile.getAbsolutePath());
            System.out.println(ioe.getMessage());
        }
        // Load the log file
        StringBuilder builder = new StringBuilder();
        try {
            try (BufferedReader reader = new BufferedReader(new FileReader(logFile))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    builder.append(line);
                    builder.append(Constants.NEW_LINE);
                }
            }
        } catch (IOException ioe) {
            System.out.println(ioe.getMessage());
        }
        this.txtLog.setText(builder.toString());
        // Get all font
        for (Font font : GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts()) {
            boolean add = true;
            //
            for (int i = 0; i < this.cbxFont.getItemCount(); i++) {
                if (this.cbxFont.getItemAt(i).equals(font.getFamily())) {
                    add = false;
                    break;
                }
            }
            //
            if (add) {
                this.cbxFont.addItem(font.getFamily());
            }
        }
    }
    
    /**
     * 
     * 
     * @param clazz
     * @param method
     * @param description
     * @param exception 
     */
    public static void log(Class clazz, Method method, String description, java.sql.SQLException exception) {
        StringBuilder builder = new StringBuilder();
        builder.append("SQL State: ");
        builder.append(exception.getSQLState());
        builder.append(Constants.NEW_LINE);
        builder.append("SQL Error: ");
        builder.append(exception.getErrorCode());
        builder.append(Constants.NEW_LINE);
        builder.append("SQL Exception: ");
        builder.append(exception.getMessage());
        builder.append(Constants.NEW_LINE);
        // Check a log any additional exceptions
        try {
            for (Throwable throwable : exception.getNextException()) {
                builder.append(Constants.TAB);
                builder.append(throwable.getMessage());
                builder.append(Constants.NEW_LINE);
            }
        } catch (NullPointerException npe) {}
        // Create log
        log(clazz, method, exception.getStackTrace()[0].getLineNumber(), description, builder.toString());        
    }
    
    /**
     * 
     * @param clazz
     * @param method
     * @param description
     * @param throwable 
     */
    public static void log(Class clazz, Method method, String description, Throwable throwable) {
        log(clazz, method, throwable.getStackTrace()[0].getLineNumber(), description, throwable.getMessage());
    }
    
    /**
     * 
     * @param className
     * @param methodName
     * @param description
     * @param throwable 
     */
    public static void log(String className, String methodName, String description, Throwable throwable) {
        log(className, methodName, throwable.getStackTrace()[0].getLineNumber(), description, throwable.getMessage());
    }
    
    /**
     * 
     * @param clazz
     * @param method
     * @param description
     * @param exception 
     */
    public static void log(Class clazz, Method method, String description, Exception exception) {
        log(clazz, method, exception.getStackTrace()[0].getLineNumber(), description, exception.getMessage());
    }
    
    /**
     * 
     * @param className
     * @param methodName
     * @param description
     * @param exception 
     */
    public static void log(String className, String methodName, String description, Exception exception) {
        log(className, methodName, exception.getStackTrace()[0].getLineNumber(), description, exception.getMessage());
    }
    
    /**
     * 
     * @param clazz
     * @param method
     * @param description
     * 
     * @param line@param message 
     */
    public static void log(Class clazz, Method method, int line, String description, String message) {
        log(clazz.getName(), method != null ? method.getName() : ".init", line, description, message);
    }
    
    /**
     * 
     * @param className
     * @param methodName
     * @param line
     * @param description
     * @param message 
     */
    public static void log(String className, String methodName, int line, String description, String message) {
        StringBuilder builder = new StringBuilder();
        builder.append("<");
        builder.append(getDate());
        builder.append(Constants.SPACE);
        builder.append(getTime());
        builder.append("> <");
        builder.append(className);
        builder.append("> <");
        builder.append(methodName);
        builder.append("> <");
        builder.append(line);
        builder.append("> <");
        builder.append(description);
        builder.append("> <");
        builder.append(message);
        builder.append(">");
        log(builder.toString());
    }
    
    /**
     * Append text to the log file
     * 
     * @param text
     */
    public static void log(String text) {
        try {
            // Checks if logging is enabled
            if (!isLogging) {
                return;
            }
            // Check if the directory exists, if not, create it
            if (!logFile.getParentFile().exists()) {
                logFile.getParentFile().mkdirs();
            }
            // Check if the log file exists, if not, create it
            if (!logFile.exists()) {
                try {
                    if (logFile.createNewFile()) {
                        log("Created log file " + getDate() + " " + getTime() + Constants.NEW_LINE);
                    } else {
                        throw new IOException("Could not create log file");
                    }
                } catch (IOException ioe) {
                    System.out.println(ioe.getMessage());
                    return;
                }
            }
            // Append the log string to the log file
            try {
                StringBuilder builder = new StringBuilder();
                try (BufferedReader reader = new BufferedReader(new FileReader(logFile))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        builder.append(line);
                        builder.append(Constants.NEW_LINE);
                    }
                }
                builder.append(text);
                if (builder.length() > 0) {
                    try (FileWriter writer = new FileWriter(logFile)) {
                        writer.append(builder.toString());
                        writer.append(Constants.NEW_LINE);
                    }
                }
            } catch (IOException ioe) {
                System.out.println(ioe.getMessage());
            }
        } finally {
            System.out.println(text);
        }
    }
    
    /**
     * 
     */
    public static void showDialog()  {
        showDialog(null);
    }
    
    /**
     * 
     * @param parent 
     */
    public static void showDialog(Frame parent)  {
        // Check if logging is enabled
        if (!isLogging) {
            return;
        }
        // Initialize the Log Systems Dialog
        LogSystem logSystem = new LogSystem(parent);
        logSystem.setLogFont(new Font("Times New Roman", Font.PLAIN, 13));
        logSystem.setVisible(true);
    }
    
    
    /**
     * Internal function to get the string formatted time
     * 
     * @return 
     */
    private static String getDate() {
        return dateFormat.format(GregorianCalendar.getInstance().getTime());
    }
    
    /**
     * Internal function to get the string formatted time
     * 
     * @return 
     */
    private static String getTime() {
        return timeFormat.format(GregorianCalendar.getInstance().getTime());
    }
    
    /**
     * Allows one to get the date format
     * 
     * @return 
     */
    public static SimpleDateFormat getDateFormat() {
        return dateFormat;
    }
    
    /**
     * Allows one to get the time format
     * 
     * @return 
     */
    public static SimpleDateFormat getTimeFormat() {
        return timeFormat;
    }
    
    /**
     * Allows one to set the date format
     * 
     * @param format 
     */
    public static void setDateFormat(SimpleDateFormat format) {
        dateFormat = format;
    }
    
    /**
     * Allows one to set the time format
     * 
     * @param format 
     */
    public static void setTimeFormat(SimpleDateFormat format) {
        timeFormat = format;
    }
    
    /**
     * 
     * @return 
     */
    public static File getLogFile() {
        return logFile;
    }
    
    /**
     * 
     * @param file 
     */
    public static void setLogFile(File file) {
        logFile = file;
    }
    
    /**
     * 
     * @return 
     */
    public static boolean isLogging() {
        return isLogging;
    }
    
    /**
     * 
     * @param logging 
     */
    public static void setLogging(boolean logging) {
        isLogging = logging;
    }
    
    /**
     * 
     * @param font 
     */
    private void setLogFont(Font font) {
        // Set the default font information
        this.cbxFont.setSelectedItem(font.getFamily());
        this.spinFontSize.setValue(font.getSize());
        this.cbxFontStyle.setSelectedIndex(font.getStyle());
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        toolbar = new javax.swing.JToolBar();
        cbxFont = new javax.swing.JComboBox();
        spinFontSize = new javax.swing.JSpinner();
        cbxFontStyle = new javax.swing.JComboBox();
        jSeparator3 = new javax.swing.JToolBar.Separator();
        btnPrint = new javax.swing.JButton();
        jScrollPane1 = new javax.swing.JScrollPane();
        txtLog = new javax.swing.JTextPane();
        jMenuBar1 = new javax.swing.JMenuBar();
        jMenu1 = new javax.swing.JMenu();
        jMenuItem3 = new javax.swing.JMenuItem();
        jSeparator1 = new javax.swing.JPopupMenu.Separator();
        btnExit = new javax.swing.JMenuItem();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        setTitle("Log System");
        setLocationByPlatform(true);
        setModal(true);
        setPreferredSize(new java.awt.Dimension(500, 400));

        toolbar.setFloatable(false);
        toolbar.setRollover(true);

        cbxFont.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                selectFont(evt);
            }
        });
        toolbar.add(cbxFont);

        spinFontSize.setModel(new javax.swing.SpinnerNumberModel(11, 8, 24, 1));
        spinFontSize.setToolTipText("Font Size");
        spinFontSize.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                selectFontSize(evt);
            }
        });
        toolbar.add(spinFontSize);

        cbxFontStyle.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Plain", "Bold", "Italic", "Bold, Italic" }));
        cbxFontStyle.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                selectFontStyle(evt);
            }
        });
        toolbar.add(cbxFontStyle);
        toolbar.add(jSeparator3);

        btnPrint.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/jm/commons/logging/icons/printer.png"))); // NOI18N
        btnPrint.setToolTipText("Print (Ctrl+P)");
        btnPrint.setFocusable(false);
        btnPrint.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        btnPrint.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        btnPrint.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                print(evt);
            }
        });
        toolbar.add(btnPrint);

        txtLog.setEditable(false);
        jScrollPane1.setViewportView(txtLog);

        jMenu1.setText("File");

        jMenuItem3.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_P, java.awt.event.InputEvent.CTRL_MASK));
        jMenuItem3.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/jm/commons/logging/icons/printer.png"))); // NOI18N
        jMenuItem3.setText("Print...");
        jMenuItem3.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                print(evt);
            }
        });
        jMenu1.add(jMenuItem3);
        jMenu1.add(jSeparator1);

        btnExit.setText("Exit");
        btnExit.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                exit(evt);
            }
        });
        jMenu1.add(btnExit);

        jMenuBar1.add(jMenu1);

        setJMenuBar(jMenuBar1);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 500, Short.MAX_VALUE)
            .addGroup(layout.createSequentialGroup()
                .addComponent(toolbar, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addGap(171, 171, 171))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addComponent(toolbar, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 348, Short.MAX_VALUE))
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents
    
    /**
     * 
     * @param evt 
     */
    private void exit(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exit
        this.setVisible(false);
        this.dispose();
    }//GEN-LAST:event_exit
    
    /**
     * 
     * @param evt 
     */
    private void print(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_print
        try {
            this.txtLog.print();
        } catch (PrinterException pe) {
            System.out.println(pe.getMessage());
        }
    }//GEN-LAST:event_print
    
    /**
     * 
     * @param evt 
     */
    private void selectFont(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_selectFont
        // Check if font is selected
        if (this.cbxFont.getSelectedItem() != null) {
            // Set the font
            this.txtLog.setFont(new Font(this.cbxFont.getSelectedItem().toString(),
                                            this.cbxFontStyle.getSelectedIndex(),
                                                ((Number) this.spinFontSize.getValue()).intValue()));
        }
    }//GEN-LAST:event_selectFont
    
    /**
     * 
     * @param evt 
     */
    private void selectFontSize(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_selectFontSize
        // Check if font is selected
        if (this.cbxFont.getSelectedItem() != null) {
            // Set the font
            this.txtLog.setFont(new Font(this.cbxFont.getSelectedItem().toString(),
                                            this.cbxFontStyle.getSelectedIndex(),
                                                ((Number) this.spinFontSize.getValue()).intValue()));
        }
    }//GEN-LAST:event_selectFontSize
    
    /**
     * 
     * @param evt 
     */
    private void selectFontStyle(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_selectFontStyle
        // Check if font is selected
        if (this.cbxFont.getSelectedItem() != null) {
            // Set the font
            this.txtLog.setFont(new Font(this.cbxFont.getSelectedItem().toString(),
                                            this.cbxFontStyle.getSelectedIndex(),
                                                ((Number) this.spinFontSize.getValue()).intValue()));
        }
    }//GEN-LAST:event_selectFontStyle
    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JMenuItem btnExit;
    private javax.swing.JButton btnPrint;
    private javax.swing.JComboBox cbxFont;
    private javax.swing.JComboBox cbxFontStyle;
    private javax.swing.JMenu jMenu1;
    private javax.swing.JMenuBar jMenuBar1;
    private javax.swing.JMenuItem jMenuItem3;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JPopupMenu.Separator jSeparator1;
    private javax.swing.JToolBar.Separator jSeparator3;
    private javax.swing.JSpinner spinFontSize;
    private javax.swing.JToolBar toolbar;
    private javax.swing.JTextPane txtLog;
    // End of variables declaration//GEN-END:variables
}
